package com.ylr.redis.framework.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wei
 * date: 2021-08-09 17:19:00
 * className: RedisStringUtils Redis-String数据结构操作工具类
 * version: 1.0
 * description:
 */
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisStringService {

    private static final Logger log = LoggerFactory.getLogger(RedisStringService.class);

    /**
     * Redis字符串类型操作模板对象
     */
    private final ValueOperations opsForValue;

    @Autowired
    public RedisStringService(RedisTemplate redisTemplate) {
        this.opsForValue = redisTemplate.opsForValue();
    }

    /**
     * 添加String类型的key-value键值对
     * 注意：
     *  1、如果原来key存在过期时间，则更新后的key变为没有过期时间
     * @param key   缓存的键
     * @param value 缓存的值（泛型值）
     * @param <T>   泛型类型
     */
    public <T> void set(final String key, final T value) {
        log.debug("String类型 set操作 key={} value={}({})", key, value, value.getClass().getSimpleName());
        opsForValue.set(key, value);
    }

    /**
     * 添加String类型的key-value键值对，并value值前面添加零个或多个占位符
     * @param key             缓存的键
     * @param value           缓存的值（泛型值）
     * @param placeholderSize value值前面填充的占位符个数
     * @param <T>             泛型类型
     */
    public <T> void set(final String key, final T value, final long placeholderSize) {
        log.debug("String类型 set操作 key={} value={}({}) placeholderSize={}", key, value, value.getClass().getSimpleName(), placeholderSize);
        opsForValue.set(key, value, placeholderSize);
    }

    /**
     * 添加String类型的key-value键值对，并根据expireTime值和timeUnit值共同设置key的过期时间
     * @param key        缓存的键
     * @param value      缓存的值（泛型值）
     * @param expireTime 过期时间
     * @param timeUnit   过期时间值类型
     * @param <T>        泛型类型
     */
    public <T> void set(final String key, final T value, final long expireTime, final TimeUnit timeUnit) {
        log.debug("String类型 set操作 key={} value={}({}) expireTime={} timeUnit={}", key, value, value.getClass().getSimpleName(), expireTime, timeUnit);
        opsForValue.set(key, value, expireTime, timeUnit);
    }

    /**
     * 添加String类型的key-value键值对，并根据时间向量设置key的过期时间
     * @param key      缓存的键
     * @param value    缓存的值（泛型值）
     * @param duration 基于时间的时间向量
     * @param <T>      泛型类型
     */
    public <T> void set(final String key, final T value, final Duration duration) {
        log.debug("String类型 set操作 key={} value={}({}) duration={}", key, value, value.getClass().getSimpleName(), duration);
        opsForValue.set(key, value, duration);
    }

    /**
     * 设置String类型缓存键key在offset下标处的bit值。
     * @param key    缓存键
     * @param offset 操作下标值
     * @param bool   设置值
     * @return offset下标更新前的值（如果offset下标没有设置过值，则默认为false值）
     */
    public Boolean setBit(final String key, final long offset, final boolean bool) {
        log.debug("String类型 setBit操作 key={} offset ={} bool={}", key, offset , bool);
        return opsForValue.setBit(key, offset , bool);
    }

    /**
     * 添加String类型的key-value键值对，当key不存在时，才进行添加，否则不添加
     * @param key   缓存键
     * @param value 缓存的值（泛型值）
     * @param <T>   泛型类型
     */
    public <T> Boolean setIfAbsent(final String key, final T value) {
        log.debug("String类型 setIfAbsent操作 key={} value={}({})", key, value, value.getClass().getSimpleName());
        return opsForValue.setIfAbsent(key, value);
    }

    /**
     * 添加String类型的key-value键值对，并根据时间向量设置键的过期时间，当key不存在时，才进行添加，否则不添加
     * @param key      缓存键
     * @param value    缓存的值（泛型值）
     * @param duration 基于时间的时间向量
     * @param <T>      泛型类型
     */
    public <T> Boolean setIfAbsent(final String key, final T value, final Duration duration) {
        log.debug("String类型 setIfAbsent操作 key={} value={}({}) duration={}", key, value, value.getClass().getSimpleName(), duration);
        return opsForValue.setIfAbsent(key, value, duration);
    }

    /**
     * 添加String类型的key-value键值对，并根据expireTime值设置key的过期时间，当key不存在时，才进行添加，否则不添加
     * @param key        缓存键
     * @param value      缓存的值（泛型值）
     * @param expireTime 过期时间（秒）
     * @param <T>        泛型类型
     */
    public <T> Boolean setIfAbsent(final String key, final T value, final long expireTime) {
        return this.setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
    }

    /**
     * 添加String类型的key-value键值对，并根据expireTime值和timeUnit值共同决定key的过期时间，当key不存在时，才进行添加，否则不添加
     * @param key        缓存键
     * @param value      缓存的值（泛型值）
     * @param expireTime 过期时间
     * @param timeUnit   过期时间值类型
     * @param <T>        泛型类型
     */
    public <T> Boolean setIfAbsent(final String key, final T value, final long expireTime, final TimeUnit timeUnit) {
        log.debug("String类型 setIfAbsent操作 key={} value={}({}) expireTime={} timeUnit={}", key, value, value.getClass().getSimpleName(), expireTime, timeUnit);
        return opsForValue.setIfAbsent(key, value, expireTime, timeUnit);
    }

    /**
     * 添加String类型的key-value键值对，当key已经存在时，才进行添加，否则不添加
     * 注意：
     *  1、如果原来key存在过期时间，则更新后的key变为没有过期时间
     * @param key   缓存键
     * @param value 缓存的值（泛型值）
     * @param <T>   泛型类型
     */
    public <T> Boolean setIfPresent(final String key, final T value) {
        log.debug("String类型 setIfPresent操作 key={} value={}({})", key, value, value.getClass().getSimpleName());
        return opsForValue.setIfPresent(key, value);
    }

    /**
     * 添加String类型的key-value键值对，并根据时间向量设置键的过期时间，当key已经存在时，才进行添加，否则不添加
     * @param key      缓存键
     * @param value    缓存的值（泛型值）
     * @param duration 基于时间的时间向量
     * @param <T>      泛型类型
     */
    public <T> Boolean setIfPresent(final String key, final T value, final Duration duration) {
        log.debug("String类型 setIfPresent操作 key={} value={}({}) duration={}", key, value, value.getClass().getSimpleName(), duration);
        return opsForValue.setIfPresent(key, value, duration);
    }

    /**
     * 添加String类型的key-value键值对，并根据expireTime值设置key的过期时间，当key已经存在时，才进行添加，否则不添加
     * @param key        缓存键
     * @param value      缓存的值（泛型值）
     * @param expireTime 过期时间（秒）
     * @param <T>        泛型类型
     */
    public <T> Boolean setIfPresent(final String key, final T value, final long expireTime) {
        return this.setIfPresent(key, value, expireTime, TimeUnit.SECONDS);
    }

    /**
     * 添加String类型的key-value键值对，并根据expireTime值和timeUnit值共同决定key的过期时间，当key已经存在时，才进行添加，否则不添加
     * @param key        缓存键
     * @param value      缓存的值（泛型值）
     * @param expireTime 过期时间
     * @param timeUnit   过期时间值类型
     * @param <T>        泛型类型
     */
    public <T> Boolean setIfPresent(final String key, final T value, final long expireTime, final TimeUnit timeUnit) {
        log.debug("String类型 setIfPresent操作 key={} value={}({}) expireTime={} timeUnit={}", key, value, value.getClass().getSimpleName(), expireTime, timeUnit);
        return opsForValue.setIfPresent(key, value, expireTime, timeUnit);
    }

    /**
     * 批量添加String类型的key-value键值对
     * 注意：
     *  1、如果map包含的key存在且有过期时间，则更新值后的key将变为没有过期时间
     * @param map 存储一对或者多对key-value的Map对象
     * @param <T> 泛型类型
     */
    public <T> void multiSet(final Map<String, T> map) {
        log.debug("String类型 multiSet操作 map={}", map);
        opsForValue.multiSet(map);
    }

    /**
     * 批量添加String类型的key-value键值对，当且仅当map里面包含的key与Redis-String存储的key重复，批量添加失败，反之批量添加成功
     * @param map 存储一对或者多对key-value的Map对象
     * @param <T> 泛型类型
     */
    public <T> Boolean multiSetIfAbsent(final Map<String, T> map) {
        log.debug("String类型 multiSetIfAbsent操作 map={}", map);
        return opsForValue.multiSetIfAbsent(map);
    }

    /**
     * 往String类型key对应的值尾部追加value值
     * @param key   缓存键
     * @param value 追加值
     * @return 字符串拼接之后的字符串长度值
     */
    public Integer append(final String key, String value) {
        log.debug("String类型 append操作 key={} value={}(String)", key, value);
        return opsForValue.append(key, value);
    }

    /**
     * 获取String类型的key对应的value值
     * @param key 缓存键
     * @param <T> 泛型类型
     */
    public <T> T get(final String key) {
        log.debug("String类型 get操作 key={}", key);
        return (T) opsForValue.get(key);
    }

    /**
     * 从String类型缓存键key对应的value值中范围截取字符串值
     * 注意：
     *  1、开始下标和结束下标为左闭有闭区间，即[startIndex,endIndex]
     * @param key        缓存键
     * @param startIndex 截取开始下标
     * @param endIndex   截取结束下标
     * @return 截取字符串得到的值
     */
    public String get(final String key, final long startIndex, final long endIndex) {
        log.debug("String类型 get操作 key={} startIndex={} endIndex={}", key, startIndex, endIndex);
        return opsForValue.get(key, startIndex, endIndex);
    }

    /**
     * 获取String类型中keys包含的一个或者多个key对应的value值
     * @param keys 所有查询的缓存键
     * @param <T>  泛型类型
     */
    public <T> List<T> multiGet(final String... keys) {
        log.debug("String类型 -- getAll操作 -- keys = {}", Arrays.toString(keys));
        return opsForValue.multiGet(Arrays.asList(keys));
    }

    /**
     * 获取String类型中keys包含的一个或者多个key对应的value值
     * @param keys 所有查询的缓存键
     * @param <T>  泛型类型
     */
    public <T> List<T> multiGet(final Collection<String> keys) {
        log.debug("String类型 multiGet操作 keys={}", keys);
        return opsForValue.multiGet(keys);
    }

    /**
     * 获取String类型的key对应的value值，并更新key对应的value值为最新值\
     * 注意：
     *  1、如果原来key存在过期时间，则更新后的key变为没有过期时间
     *  2、如果key不存在，则返回null值，并新添加一对key-value键值对
     * @param key   缓存键
     * @param value 最新缓存值
     * @param <T>   泛型类型（返回结果）
     * @param <V>   泛型类型（最新值）
     */
    public <T,V> T getAndSet(final String key, final V value) {
        log.debug("String类型 multiGet操作 key={} value={}({})", key, value, value.getClass().getSimpleName());
        return (T) opsForValue.getAndSet(key, value);
    }

    /**
     * 获取String类型缓存键key在offset下标出的比特位
     * @param key    缓存键
     * @param offset 取值下标
     * @return 比特位值[存在-true 不存在-false]
     */
    public Boolean getBit(final String key, final long offset) {
        log.debug("String类型 getBit操作 key={} offset={}", key, offset);
        return opsForValue.getBit(key, offset);
    }

    /**
     * String类型的缓存键key对应的value值自增
     * 注意：
     * 1、如果key对应的值不为数字类型，则报异常
     * 2、如果key对应的值，超过Long类型的最大值，则报溢出异常
     * 3、如果key不存在，则新添加一对key-value键值对，value值为1
     * @param key 自增的键
     * @return 旧value值加上num值后的最新值
     */
    public Long increment(final String key) {
        log.debug("String类型 increment操作 key={}", key);
        try {
            return opsForValue.increment(key);
        } catch (Exception e) {
            log.error("String类型 increment操作 key={}。异常信息： {}", key, e.getMessage());
        }
        return null;
    }

    /**
     * String类型的缓存键key对应的value值加上num值
     * 注意：
     * 1、如果key对应的值不为数字类型，则报异常
     * 2、如果key对应的值，超过Long类型的最大值，则报溢出异常
     * 3、如果key不存在，则新添加一对key-value键值对，value值为num
     * @param key 缓存键
     * @param num 增加的数值大小
     * @return 旧value值加上num值后的最新值
     */
    public Long increment(final String key, final long num) {
        log.debug("String类型 increment操作 key={} num={}", key, num);
        try {
            return opsForValue.increment(key, num);
        } catch (Exception e) {
            log.error("String类型 increment操作 key={} num={}。异常信息： {}", key, num, e.getMessage());
        }
        return null;
    }

    /**
     * String类型的缓存键key对应的value值加上num值
     * 注意：
     * 1、如果key对应的值不为数字类型，则报异常
     * 2、如果key对应的值，超过Long类型的最大值，则报溢出异常
     * 3、如果key不存在，则新添加一对key-value键值对，value值为num
     * @param key 缓存键
     * @param num 增加的数值大小
     * @return 旧value值加上num值后的最新值
     */
    public Double increment(final String key, final double num) {
        log.debug("String类型 increment操作 key={} num={}", key, num);
        try {
            return opsForValue.increment(key, num);
        } catch (Exception e) {
            log.error("String类型 increment操作 key={} num={}。异常信息： {}", key, num, e.getMessage());
        }
        return null;
    }

    /**
     * String类型的键key对应的值自减
     * 注意：
     *  1、如果key对应的值不为数字类型，则报异常
     *  2、如果key对应的值，超过Long类型的最小值，则报溢出异常
     *  3、如果key不存在，则新添加一对key-value键值对，value值为-1
     * 如果key对应的值不为数字类型，则报异常
     * @param key 自减的键
     */
    public Long decrement(final String key) {
        log.debug("String类型 decrement操作 key={}", key);
        try {
            return opsForValue.decrement(key);
        } catch (Exception e) {
            log.error("String类型 decrement操作异常 key={}。异常信息： {}", key, e.getMessage());
        }
        return null;
    }

    /**
     * String类型的键key对应的值自减
     * 注意：
     *  1、如果key对应的值不为数字类型，则报异常
     *  2、如果key对应的值，超过Long类型的最小值，则报溢出异常
     *  3、如果key不存在，则新添加一对key-value键值对，value值为-1
     * 如果key对应的值不为数字类型，则报异常
     * @param key 自减的键
     */
    public Long decrement(final String key, final long num) {
        log.debug("String类型 decrement操作 key={} num={}", key, num);
        try {
            return opsForValue.decrement(key, num);
        } catch (Exception e) {
            log.error("String类型 decrement操作异常 key={} num={}。异常信息： {}", key, num, e.getMessage());
        }
        return null;
    }

    /**
     * 获取String类型的缓存键key对应value值的字符个数
     * @param key 缓存键
     * @return value值的字符个数
     */
    public Long size(final String key) {
        log.debug("String类型 size操作 key={}", key);
        return opsForValue.size(key);
    }

}
